#include"test.hpp"

const int processnum = 5; //五个子进程

vector<task_t> tasks;


// 管理管道
// 先描述
class channel
{
public:
    //管道文件的构造函数
    channel(int cmdcfd, int slarverid, string processname)
        :_cmdcfd(cmdcfd)
        ,_slarverid(slarverid)
        ,_processname(processname)
    {}
public:
    int _cmdcfd;//文件描述符， 表明父进程向管道的哪一端去写入。  
    pid_t _slarverid;   //子进程的PID

    string _processname; //子进程的名字。——方便打日志。
};

void slaver()
{
    //read();
    while (true)
    {
        int cmdcode = 0;
        int n = read(0, &cmdcode, sizeof(int));    //如果读端不给写端任务， 那么读端阻塞!

        if (n == sizeof(int))
        {
            //执行对应的cmdcode对应的任务列表
            cout << "slaver get a command : " <<  getpid() << " : " << "cmdcode: " << cmdcode << endl; 
            if (cmdcode >= 0 && cmdcode < tasks.size()) tasks[cmdcode]();   //只要任务码在范围之内， 就让读端进程执行该任务
        }
        if (n == 0) break;  //如果读到了0， 说明写端关闭， 读端读到文件末尾， 就需要停止读取了。 
    }
    cout << "Process PID: " << getpid() << " quit" << endl;
}



//编码规范, 写任何一个函数:输入:const &,输出 *, 输入输出:& .   
void InitProcess(vector<channel>* channels)
{
    //version2 确保每一个子进程都只有一个写端
    vector<int> oldfds;   //创建一个数组， 用来保存父进程占用的文件描述符。

    for (int i = 0; i < processnum; i++)
    {
        int pipefd[2]; //临时空间
        int n = pipe(pipefd);

        assert(!n);//校验代码
        (void)n;   //校验代码


        for (auto fd : oldfds) close(fd);  //遍历整个数组， 将已经占用的文件描述符关闭。
        pid_t id = fork();  //创建子进程
        if (id == 0) //child
        {
            //关闭读写端
            close(pipefd[1]);      //父进程要写， 子进程要读。 所以要关闭1号写端
            //核心代码

            dup2(pipefd[0], 0);//将0号描述符， 也就是标准输入替换成管道的读端
            slaver();

            //退出
            exit(0);
        }
        //father
        close(pipefd[0]);          //父进要写， 所以关闭的是读端
    
        //走到这里， 就已经建立了我们的通信信道。 添加字段
        //我们规定第i个进程叫做process + i
        string name = "process-" + to_string(i);
        channels->push_back(channel(pipefd[1], id, name));  
        //完成后， 我们就已经完成了循环创建5个子进程。 
        oldfds.push_back(pipefd[1]); //记录当前父进程的pipefd[1]里面的占用的文件描述符
    }
}



void Debug(const vector<channel>& channels)
{
    for (int i = 0; i < processnum; i++)
    {
        cout << channels[i]._cmdcfd << " " << channels[i]._processname << " " << channels[i]._slarverid << endl;
    }

}

void Menu()
{
    cout << "*************************************************" << endl;
    cout << "******  1.更新日志        2.更新野区         ******" << endl;
    cout << "******  3.检测更新        3.释放技能         ******" << endl;
    cout << "*************************************************" << endl;
}

void CtrlSlaver(vector<channel> channels)
{
    int which = 0;   //轮询法

    int select = 0;
    while (true)
    {
        //1.选择任务， 选择进程
        Menu();

        cin >> select;
        if (select <= 0 || select >= tasks.size()) break;  //如果select是小于等于0或者大于等于tasks.size()， 那么就退出。
        int cmdcode = select;

        //2.选择进程 
        //我们先择进程派发任务的时候， 要把我们的任务， 均衡的派发给进程任务。——这个过程， 叫做负载均衡——这个过程如何做？——第一种随机数， 第二种轮询
        // int processpos = rand() % channels.size(); //随机数法

        cout << "father say cmdcode : " << cmdcode 
            << " already sendto " << channels[which]._cmdcfd 
                << " processname : " << channels[which]._processname << endl;   //测试代码打印
        //3.发送任务
        write(channels[which]._cmdcfd, &cmdcode, sizeof(cmdcode));   //第一个参数为fd输入性参数， 第二个为cmdcode写的数据的首地址。 第三个为要写入的字节数。

        which++;
        which %= channels.size();
        sleep(1);

    }
}

void QuitProcess(const vector<channel> &channels)
{
    for (const auto e : channels) close(e._cmdcfd);  //写端
    for (const auto e : channels) waitpid(e._slarverid, nullptr, 0);
}


int main()
{
    LoadTask(&tasks);
    
    vector<channel> channels;    //管道文件的再组织。  以后， 如果系统想要管理我们的管道文件的话，那么只需要管理我们的channels的元素即可。 

    
    //创建进程
    InitProcess(&channels);

    //Debug
    Debug(channels);
    
    //控制子进程
    CtrlSlaver(channels);


    //3.清理收尾

    QuitProcess(channels);

    //清理资源。
    return 0;
}